/**
 * 
 */
package br.com.skynix.tcmc.base.spring;

import java.lang.reflect.ParameterizedType;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.primefaces.model.SortOrder;

import br.com.skynix.tcmc.base.excecao.MensagemErro;
import br.com.skynix.tcmc.base.excecao.MensagensSistema;
import br.com.skynix.tcmc.base.excecao.NegocioException;
import br.com.skynix.tcmc.base.model.EntidadeAuditavel;
import br.com.skynix.tcmc.model.financeiro.Natureza;
import br.com.skynix.tcmc.model.rh.Empresa;
import br.com.skynix.tcmc.model.seguranca.Usuario;
import br.com.skynix.tcmc.util.WebUtil;

/**
 * @author mgrb
 * 
 */
public abstract class AbstractDao<T extends EntidadeAuditavel> {
	@PersistenceContext(name = "tcmc-persistence-unit")
	protected EntityManager entityManager;

	private Class<T> classePersistente;

	/**
	 * Construtor do Dao.
	 */
	@SuppressWarnings("unchecked")
	public AbstractDao() {
		super();
		this.classePersistente = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * Insere uma instancia de {@link Entidade} no Banco de dados.
	 * @param entidade instancia de {@link Entidade}.
	 * @return nova chave da entidade.
	 */
	public Long inserir(T entidade) {
		preencherDadosAuditaveis(entidade);
		entityManager.persist(entidade);
		return entidade.getId();
	}

	/**
	 * Busca a Entidade pela chave primaria.
	 * @param id Instancia da Chave primaria.
	 * @return Instancia de {@link Entidade}.
	 */
	public T buscarPelaChave(Long id) {
		return entityManager.find(classePersistente, id);
	}

	/**
	 * Atualiza a instancia de Entidade no banco de dados.
	 * @param entidade instancia de {@link Entidade}.
	 */
	public void alterar(T entidade) {
		preencherDadosAuditaveis(entidade);
		entityManager.merge(entidade);
	}

	public void excluir(T entidade) {
		entidade.setDeletado(true);
		entidade.setAtivo(false);
		preencherDadosAuditaveis(entidade);
		entityManager.merge(entidade);
	}

	/**
	 * @param id
	 * @return
	 */
	public Integer getVersaoAtual(Long id) {
		Criteria criteria = criarCriteria(classePersistente);
		criteria.add(Restrictions.idEq(id));
		criteria.setProjection(Projections.max("versao"));
		return (Integer) criteria.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	public List<T> buscarPeloExemplo(T entidadeExemplo) {
		Criteria criteria = criarCriteria(entidadeExemplo);
		return Collections.checkedList(criteria.list(), classePersistente);
	}

	/**
	 * @param entidadeExemplo
	 * @return
	 */
	protected Criteria criarCriteria(T entidadeExemplo) {
		Example example = Example.create(entidadeExemplo);
		example = example.enableLike(MatchMode.ANYWHERE);
		example = example.ignoreCase();

		Criteria criteria = criarCriteria(entidadeExemplo.getClass());
		criteria.add(example);
		return criteria;
	}

	/**
	 * @return
	 */
	protected Criteria criarCriteria(Class<? extends EntidadeAuditavel> clazz) {
		Session session = (Session) entityManager.getDelegate();
		Criteria criteria = session.createCriteria(clazz);
		return criteria;
	}

	private void preencherDadosAuditaveis(T entidadeAuditavel) {
		HttpServletRequest request = (HttpServletRequest) (FacesContext
				.getCurrentInstance().getExternalContext().getRequest());
		entidadeAuditavel.setIp(request.getRemoteAddr());
		Usuario usuario = (Usuario) request.getSession().getAttribute(
				WebUtil.USER_UUID);
		entidadeAuditavel.setResponsavel(usuario.getUsername());
		if (entidadeAuditavel.getDataCriacao() == null) {
			entidadeAuditavel.setDataCriacao(new Timestamp(System
					.currentTimeMillis()));
		}
		entidadeAuditavel.setDataUltimaAtualizacao(new Timestamp(System
				.currentTimeMillis()));
	}

	/**
	 * @return
	 */
	public List<T> listarTodos() {
		return criarCriteria(classePersistente).list();
	}

	/**
	 * @param modelo
	 * @param first
	 * @param pageSize
	 * @param sortField
	 * @param sortOrder
	 * @return
	 */
	public List<T> buscarPeloExemplo(T modelo, int first, int pageSize,
			String sortField, SortOrder sortOrder) {

		Criteria criteria = criarCriteria(modelo);
		criteria.setFirstResult(first);
		criteria.setMaxResults(pageSize);
		if (StringUtils.isNotBlank(sortField)) {
			if (SortOrder.ASCENDING.equals(sortOrder)) {
				criteria.addOrder(Order.asc(sortField));
			}
			else if (SortOrder.DESCENDING.equals(sortOrder)) {
				criteria.addOrder(Order.desc(sortField));
			}
		}

		return criteria.list();
	}

	/**
	 * @param modelo
	 * @return
	 */
	public int getQuantidadeRegistros(T modelo) {
		Criteria criteria = criarCriteria(modelo);
		criteria.setProjection(Projections.rowCount());
		Object rowCount = criteria.uniqueResult();
		return rowCount == null ? 0 : ((Long) rowCount).intValue();
	}

	/**
	 * @param empresa
	 * @return
	 */
	public List<Natureza> listarTodos(Empresa empresa) {
		T exemplo = null;
		MensagemErro erro = null;
		try {
			exemplo = classePersistente.newInstance();
		}
		catch (InstantiationException exception) {
			erro = new MensagemErro(MensagensSistema.ERRO_MSG_RUNTIME,
					exception.getMessage());
		}
		catch (IllegalAccessException exception) {
			erro = new MensagemErro(MensagensSistema.ERRO_MSG_RUNTIME,
					exception.getMessage());
		}
		if (erro != null) {
			throw new NegocioException(erro);
		}
		Criteria criteria = criarCriteria(exemplo);
		criteria.createAlias("empresa", "empresa");
		return criteria.list();
	}
}
